﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace JobDemo01.Job
{
    public class PatternResult
    {
        public int Index => Image.Index;

        public bool RequireManualAssist { get; set; }

        public ImagingResult Image { get; set; }

        public (ImageData, double)[] Values { get; set; } = [];
    }

    class ProcessingPipeline(MessageQueue<PatternResult> queue,
        IImagingService imagingService,
        FrameDispatcher dispatcher,
        JobEnvironment env,
        JobRunOptions options)
    {
        [DllImport("Metrology.dll")]
        extern static int CDMeasureTestMulti(ref AMPParam measureOptionBuf, ref PositionBuf positionBuf, int posNum, byte[] pImage, int imgx, int imgy, float pixelSize, ref DrawOption drawOption,
                                     byte[] pResultpImage, ref CDResult pCDPointBuf);

        [DllImport("Metrology.dll")]
        extern static int CDMeasureTestAuto(ref AMPParam measureOptionBuf, byte[] pImage, int imgx, int imgy, float pixelSize, ref DrawOption drawOption,
                                        byte[] pResultpImage, ref CDResult pCDPointBuf);

        public async IAsyncEnumerable<PatternResult> Process(IAsyncEnumerable<ImagingResult> source)
        {
            await foreach (var pImg in source.ConfigureAwait(false))
            {
                switch (pImg.Kind)
                {
                    case PatternKind.Align:
                        DispatchWithMarker(pImg, 500);
                        goto case PatternKind.High;
                    // OM frames already dispatched
                    case PatternKind.High:
                        if (!pImg.ThresholdReached && env.ManualAssistAlignment)
                        {
                            queue.Send(new PatternResult
                            {
                                Image = pImg,
                                RequireManualAssist = true
                            });
                            break;
                        }
                        else
                        {
                            var img = new PatternResult
                            {
                                Image = pImg,
                            };
                            queue.Send(img);
                            yield return img;
                        }
                        break;
                    case PatternKind.Addr:
                        DispatchWithMarker(pImg, 500);
                        if (pImg.ThresholdReached)
                        {
                            // addressing images will be sent downstream with mp
                            queue.Send(new PatternResult
                            {
                                Image = pImg,
                            });
                        }
                        else
                        {
                            if (env.ManualAssistAddressing)
                            {
                                queue.Send(new PatternResult
                                {
                                    Image = pImg,
                                    RequireManualAssist = true
                                });
                                break;
                            }

                            // there will be no mp from upstream if addressing fails
                            yield return new PatternResult
                            {
                                Image = new ImagingResult
                                {
                                    Index = pImg.Index,
                                    Kind = PatternKind.Mp,
                                    Score = -1,
                                    Threshold = pImg.Pattern.Reference.Threshold,
                                    Coordinate = pImg.Coordinate,
                                    Pattern = pImg.Pattern,
                                    Addressing = pImg,
                                }
                            };
                        }
                        break;
                    case PatternKind.PreDose:
                    case PatternKind.Focus:
                        Dispatch(pImg, 30);
                        break;
                    case PatternKind.Mp:
                        if (options.SkipMeasurement)
                        {
                            Dispatch(pImg, 500);
                            break;
                        }

                        if (!pImg.ThresholdReached)
                        {
                            Dispatch(pImg, 500);
                            queue.Send(new PatternResult
                            {
                                Image = pImg,
                                RequireManualAssist = env.ManualAssistMeasurement
                            });
                            break;
                        }

                        var pattern = pImg.Pattern;
                        var values = new (ImageData, double)[pattern.CDPoints.Count];
                        for (var i = 0; i < pattern.CDPoints.Count; i++)
                        {
                            var spec = pattern.CDPoints[i];
                            var mp = pImg.ImageData;
                            var param = spec.AMPParam;
                            var settings = pattern.Reference.ScanSettings;
                            var pixelSize = settings.FOV / settings.ScanWidth * 1000;
                            var copy = new byte[mp.Data.Length];
                            mp.Data.CopyTo(copy, 0);
                            var drawOptions = spec.DrawOption;
                            var result = new CDResult();
                            if (param.ACD == 0)
                            {
                                var posBuf = spec.PositionBuf;
                                posBuf.CDPos = new PositionInfo[posBuf.CDPos.Length];
                                for (var j = 0; j < param.target; j++)
                                {
                                    posBuf.CDPos[j] = spec.PositionBuf.CDPos[j];
                                    switch (param.measureType)
                                    {
                                        case 0:
                                        case 1:
                                        case 2:
                                            posBuf.CDPos[j].ix0 = Math.Clamp(posBuf.CDPos[j].ix0 + (int)pImg.MatchDx, 0, settings.ScanWidth - 1);
                                            posBuf.CDPos[j].ix1 = Math.Clamp(posBuf.CDPos[j].ix1 + (int)pImg.MatchDx, 0, settings.ScanWidth - 1);
                                            posBuf.CDPos[j].iy0 = Math.Clamp(posBuf.CDPos[j].iy0 + (int)pImg.MatchDy, param.sumLinePoint, settings.ScanWidth - 1 - param.sumLinePoint);
                                            posBuf.CDPos[j].iy1 = Math.Clamp(posBuf.CDPos[j].iy1 + (int)pImg.MatchDy, param.sumLinePoint, settings.ScanWidth - 1 - param.sumLinePoint);
                                            break;
                                        case 3:
                                            posBuf.CDPos[j].x_center = Math.Clamp(posBuf.CDPos[j].x_center + (int)pImg.MatchDx, 0, settings.ScanWidth - 1);
                                            posBuf.CDPos[j].y_center = Math.Clamp(posBuf.CDPos[j].y_center + (int)pImg.MatchDy, 0, settings.ScanWidth - 1);
                                            break;
                                    }
                                }
                                _ = CDMeasureTestMulti(ref param, ref posBuf, param.target, mp.Data, mp.Width, mp.Height, (float)pixelSize, ref drawOptions, copy, ref result);
                            }
                            else
                            {
                                _ = CDMeasureTestAuto(ref param, mp.Data, mp.Width, mp.Height, (float)pixelSize, ref drawOptions, copy, ref result);
                            }

                            var img = new ImageData(mp.Width, mp.Height, copy, PixelFormats.Gray8);
                            Dispatch(img, 500);
                            double res = param.measureType switch
                            {
                                1 => result.sigma,
                                2 => (result.sigmaLeftEdge + result.sigmaRightEdge) / 2,
                                _ => result.average,
                            };
                            res *= pixelSize;
                            values[i] = (img, res);
                        }

                        var v = new PatternResult
                        {
                            Image = pImg,
                            Values = values,
                        };
                        queue.Send(v);
                        yield return v;
                        break;
                }
            }
        }

        void Dispatch(ImageData img, object data, int time)
        {
            if (imagingService.Mode == 2)
            {
                dispatcher.Dispatch(img, data, time);
            }
        }

        void Dispatch(ImageData img, int time)
        {
            Dispatch(img, null, time);
        }

        void Dispatch(ImagingResult pImg, int time)
        {
            Dispatch(pImg.ImageData, pImg.Payload, time);
        }

        void DispatchWithMarker(ImagingResult pImg, int time)
        {
            var img = pImg.ImageData;
            var b = new byte[img.Data.Length];
            Array.Copy(img.Data, b, b.Length);
            var copy = img with { Data = b };
            Marker.Draw(copy, pImg.MatchDx, pImg.MatchDy);
            Dispatch(copy, pImg.Payload, time);
        }
    }
}
